package pl.edu.agh.lab2.sr.gameServer;

import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;

import pl.edu.agh.lab2.sr.beans.Field;
import pl.edu.agh.lab2.sr.beans.GameMap;
import pl.edu.agh.lab2.sr.beans.GameResponses;
import pl.edu.agh.lab2.sr.beans.GameStates;
import pl.edu.agh.lab2.sr.beans.IPlayer;
import pl.edu.agh.lab2.sr.beans.InvalidGameStateException;
import pl.edu.agh.lab2.sr.beans.InvalidUserException;
import pl.edu.agh.lab2.sr.beans.MyFieldStates;
import pl.edu.agh.lab2.sr.beans.OpponentFieldStates;
import pl.edu.agh.lab2.sr.remoteInterfaces.IBattleshipGame;
import pl.edu.agh.lab2.sr.remoteInterfaces.IServerListener;

public class BattleshipGame extends UnicastRemoteObject implements
		IBattleshipGame {

	protected BattleshipGame() throws RemoteException {
		super();
	}

	private IPlayer player1;
	private IPlayer player2;
	private IServerListener listener1;
	private IServerListener listener2;
	private GameMap map1;
	private GameMap map2;

	private Object playersLock;
	private Object mapsLock;

	private GameStates gameState;

	private int gameId;

	public BattleshipGame(IPlayer player1, IServerListener listener1, int gameId)
			throws RemoteException {
		super();
		this.player1 = player1;
		this.listener1 = listener1;
		this.gameId = gameId;
		this.playersLock = new Object();
		this.mapsLock = new Object();
		this.gameState = GameStates.Initializing;
	}

	/**
	 * 
	 */
	private static final long serialVersionUID = 850535850054723107L;

	@Override
	public IPlayer getPlayer1() {
		return player1;
	}

	@Override
	public IPlayer getPlayer2() {
		return player2;
	}

	public void join(IPlayer player2, IServerListener listener2)
			throws RemoteException, InvalidUserException,
			InvalidGameStateException {

		if (!isPlayer2SlotFree()) {
			throw new InvalidGameStateException(
					"There are no free slots in game");
		}

		if (isPlayerIsRegistered(player2)) {
			throw new InvalidUserException("Player already registered: "
					+ player2.getNick());
		}

		this.player2 = player2;
		this.listener2 = listener2;

		notifyPlayer1("[P] player 1 has joined game: " + player2.getNick());
		notifyPlayer2("[P] welcome, you have joined game");
	}

	private boolean isPlayer2SlotFree() {
		boolean isFree = false;
		synchronized (playersLock) {
			if (player2 == null)
				isFree = true;
		}
		return isFree;
	}

	@Override
	public int getId() throws RemoteException {
		return gameId;
	}

	@Override
	public String getInfoAboutGame() {
		StringBuilder sb = new StringBuilder();
		sb.append("Id: " + gameId);
		sb.append(", state: " + gameState);
		sb.append(", P1: ");
		if (player1 != null) {
			sb.append(player1.getNick());
		} else {
			sb.append("---");
		}
		sb.append(", P2: ");
		if (player2 != null) {
			sb.append(player2.getNick());
		} else {
			sb.append("---");
		}
		sb.append("\n");
		return sb.toString();
	}

	@Override
	public void initMap(IPlayer player, GameMap map) throws RemoteException,
			InvalidUserException, IllegalArgumentException {

		if (!isPlayerIsRegistered(player)) {
			throw new InvalidUserException("Player not registered in game: "
					+ player.getNick());

		}
		if (player.equals(player1)) {
			if (map1 != null) {
				throw new IllegalArgumentException("Map1 already initialized");
			}
			map1 = map;
			System.out.println("[G] map1 initialized");
		} else {
			if (map2 != null) {
				throw new IllegalArgumentException("Map2 already initialized");
			}
			map2 = map;
			System.out.println("[G] map2 initialized");
		}

	}

	@Override
	public void startGame(IPlayer player) throws RemoteException,
			InvalidGameStateException, InvalidUserException {

		synchronized (gameState) {
			if (gameState != GameStates.Initializing) {
				throw new InvalidGameStateException("[G] Game already started");
			}
		}

		synchronized (playersLock) {
			if (player1 == null) {
				throw new InvalidGameStateException(
						"[G] Player1 not initialized");
			}
			if (player2 == null) {
				throw new InvalidGameStateException(
						"[G] Player2 not initialized");
			}
		}

		synchronized (mapsLock) {
			if (map1 == null) {
				throw new InvalidGameStateException("[G] Map1 not initialized");
			}
			if (map2 == null) {
				throw new InvalidGameStateException("[G] Map2 not initialized");
			}
		}

		// check if game is initialized in proper way
		notifyAllPlayers("[P] GAME STARTED!!!");
		gameState = GameStates.PlayerOneMove;
		notifyPlayer1("[P] Your move");
		notifyPlayer2("[P] Player1 move");
	}

	@Override
	public GameResponses move(IPlayer player, Field field)
			throws RemoteException, InvalidUserException,
			InvalidGameStateException {

		if (!isPlayerIsRegistered(player)) {
			throw new InvalidUserException("player not registered: "
					+ player.getNick());
		}

		GameResponses response = null;
		synchronized (gameState) {
			if (gameState != GameStates.PlayerOneMove
					&& gameState != GameStates.PlayerTwoMove) {
				throw new InvalidGameStateException("[G] Bad game state");
			}

			synchronized (playersLock) {

				if (player.equals(player1)) {
					if (gameState != GameStates.PlayerOneMove) {
						throw new InvalidUserException("[G] Not your move");
					}

					response = handlePlayer1Move(field);
					notifyPlayer2("[P] Player1 " + player.getNick()
							+ " shooted: " + field.toString() + ": " + response);

				} else {
					if (gameState != GameStates.PlayerTwoMove) {
						throw new InvalidUserException("[G] Not your move");
					}
					response = handlePlayer2Move(field);
					notifyPlayer1("[P] Player2 " + player.getNick()
							+ " shooted: " + field.toString() + ": " + response);

				}
			}
		}
		return response;
	}

	private GameResponses handlePlayer1Move(Field field) {
		MyFieldStates state = map2.getMyCell(field);
		GameResponses response = null;
		switch (state) {
		case Empty: {
			map1.setOpponentCell(field, OpponentFieldStates.MissedShot);
			map2.setMyCell(field, MyFieldStates.MissedShot);
			response = GameResponses.ShotMissed;
			gameState = GameStates.PlayerTwoMove;
			break;
		}
		case MissedShot:
		case HittedShip: {
			response = GameResponses.AlreadyShooted;
			break;
		}
		case Ship: {
			map1.setOpponentCell(field, OpponentFieldStates.HittedShip);
			map2.setMyCell(field, MyFieldStates.HittedShip);
			response = GameResponses.HittedShip;
			break;
		}
		}

		return response;

	}

	private GameResponses handlePlayer2Move(Field field) {
		MyFieldStates state = map1.getMyCell(field);
		GameResponses response = null;
		switch (state) {
		case Empty: {
			map2.setOpponentCell(field, OpponentFieldStates.MissedShot);
			map1.setMyCell(field, MyFieldStates.MissedShot);
			response = GameResponses.ShotMissed;
			gameState = GameStates.PlayerOneMove;
			break;
		}
		case MissedShot:
		case HittedShip: {
			response = GameResponses.AlreadyShooted;
			break;
		}
		case Ship: {
			map2.setOpponentCell(field, OpponentFieldStates.HittedShip);
			map1.setMyCell(field, MyFieldStates.HittedShip);
			response = GameResponses.HittedShip;
			break;
		}
		}

		return response;
	}

	private void notifyPlayer1(String msg) throws RemoteException {
		listener1.onServerNotify(msg);
	}

	private void notifyPlayer2(String msg) throws RemoteException {
		listener2.onServerNotify(msg);
	}

	private void notifyAllPlayers(String msg) throws RemoteException {
		notifyPlayer1(msg);
		notifyPlayer2(msg);
	}

	@Override
	public GameMap getMap(IPlayer player) throws RemoteException,
			InvalidUserException {

		if (!isPlayerIsRegistered(player)) {
			throw new InvalidUserException("Player not registered in game: "
					+ player.getNick());
		}
		if (player.equals(player1)) {
			System.out.println("[G] getMap to player 1");
			return map1;
		} else {
			System.out.println("[G] getMap to player 2");
			return map2;
		}
	}

	private boolean isPlayerIsRegistered(IPlayer player) {
		boolean isRegistered = false;
		synchronized (playersLock) {
			if (player.equals(player1))
				isRegistered = true;
			if (!isRegistered) {
				if (player.equals(player2))
					isRegistered = true;
			}
		}
		return isRegistered;
	}

}
